package com.ofek.eventos;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;

import com.ofek.eventos.db.datatypes.EventData;
import com.ofek.eventos.db.datatypes.Sync;
import com.ofek.eventos.db.datatypes.UserCredential;
import com.ofek.eventos.gui.list_table.EventsListTableGUI;
import com.ofek.eventos.gui.list_table.ListTable;
import com.ofek.eventos.gui.list_table.ListTableModel;
import com.ofek.eventos.net.ocsf.server.Service;
import com.ofek.utils.Log;

public class EventosService extends Service {
	private final File usersFile;
	private final File eventosFolder;
	private final File eventsFile;
	public EventosService() throws IOException, ClassNotFoundException {
		super(7879);
		eventosFolder = new File("c://eventos");
		eventosFolder.mkdirs();
		usersFile = new File(eventosFolder,"users.dat");
		eventsFile = new File(eventosFolder,"events.dat");
		EventsListTableGUI.getInstance().onDeleteUsers(() -> {
			synchronized (usersFile) {
				usersFile.delete();
				users = new HashMap<String,UserCredential>();
				showAllEvents();
			}	
		});
		EventsListTableGUI.getInstance().onDeleteEvents(() -> {
			synchronized (eventsFile) {
				eventsFile.delete();
				events = new HashMap<String, ArrayList<EventData>>();
				showAllEvents();
			}	
		});
		try {
			final ObjectInputStream objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(usersFile)));
			users=(HashMap<String, UserCredential>) objectInputStream.readObject();	
			objectInputStream.close();
		} catch (final Exception e) {
			users = new HashMap<String,UserCredential>();
			usersFile.delete();
		}
		try {
			final ObjectInputStream objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(eventsFile)));
			events=(HashMap<String, ArrayList<EventData>>) objectInputStream.readObject();
			objectInputStream.close();
		} catch (final Exception e) {
			events = new HashMap<String, ArrayList<EventData>>();
			eventsFile.delete();
		}
		for ( final Entry<String, ArrayList<EventData>> e : events.entrySet()) {
			for  ( final EventData  event : e.getValue() ) {
				if (serverTS<event.orderTS) serverTS=event.orderTS;
			}
		}
		serverTS++;
		showAllEvents();
	}

	private HashMap<String,UserCredential> users;
	private HashMap<String,ArrayList<EventData>> events;
	private long serverTS;

	public boolean login(final UserCredential u) {
		Log.d("user logged in : " + u);
		return users.get(u.id).pw.equals(u.pw);
	}
	public boolean register(final UserCredential u) throws IOException {
		if (users.containsKey(u.id)) {
			Log.d("tried to register existing user : " + u);
			//return false;
			return true;
		}
		users.put(u.id,u);

		synchronized (usersFile) {
			final ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(usersFile)));
			objectOutputStream.writeObject(users);
			objectOutputStream.close();
			Log.d("registered user : " + u);
		}
		return true;

	}



	public Sync sync(final Sync sync) throws IOException {
		final String userName = sync.userName;
		addToServer(sync);
		final Sync response = buildResponse(sync);
		//TODO: deleting events might effect lastModified!
		showAllEvents();

		synchronized (eventsFile) {
			final ObjectOutputStream objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(eventsFile)));
			objectOutputStream.writeObject(events);
			objectOutputStream.close();

		}
		Log.d("\nrecieved sync "+sync+"\nsent back "+response+"\n");
		return response;
	}
	private Sync buildResponse(final Sync sync) {
		final ArrayList<EventData> newEventsForClient = new ArrayList<EventData>();
		final HashMap<String, Long> serverLastKnowns = new HashMap<String, Long>();
		final HashMap<String, Long> clientCurrentKnowns = sync.currentKnown;

		//Add all events of users that are already known to the client
		for (final Entry<String, Long> entry : clientCurrentKnowns.entrySet()) {
			final String uid = entry.getKey();
			Long serverLastKnown = serverLastKnowns.get(uid);
			if ( serverLastKnown==null ) 
				serverLastKnowns.put(uid, serverLastKnown=(long) 0);
			final ArrayList<EventData> arrayList = getEventsOfUser(uid);
			final Long clientLastKnown = entry.getValue();
			for (int i = 0 ; i < arrayList.size() ;i++) {
				final EventData e = arrayList.get(i);
				if (e.ts > clientLastKnown && shouldSendEvent(e,sync.userName)) 
					newEventsForClient.add(e);
				if (serverLastKnown<e.ts && shouldSendEvent(e,sync.userName))
					serverLastKnowns.put(uid, serverLastKnown=e.ts);
			}
		}

		//Add all events of users that are not known to the client
		for (final Entry<String, ArrayList<EventData>> eventsPerUser : events.entrySet()) {
			if (!clientCurrentKnowns.containsKey(eventsPerUser.getKey())) {
				for (int i = 0 ; i < eventsPerUser.getValue().size() ;i++) {
					final EventData e = eventsPerUser.getValue().get(i);
					if ( !shouldSendEvent(e,sync.userName) ) continue;
					newEventsForClient.add(e);
					long serverLastKnown;
					if ( !serverLastKnowns.containsKey(eventsPerUser.getKey()) ) 
						serverLastKnowns.put(eventsPerUser.getKey(),serverLastKnown=0);
					else 
						serverLastKnown = serverLastKnowns.get(eventsPerUser.getKey());
					if (serverLastKnown<e.ts)
						serverLastKnowns.put(eventsPerUser.getKey(), serverLastKnown=e.ts);
				}
			}
		}

		final Sync response = new Sync();
		response.userName = sync.userName;
		response.currentKnown =  serverLastKnowns;
		Collections.sort(newEventsForClient);
		response.newEvents = newEventsForClient;
		return response;
	}
	private ArrayList<EventData> getEventsOfUser(final String uid) {
		ArrayList<EventData> arrayList = events.get(uid);
		if ( arrayList == null ) 
			events.put(uid, arrayList=new ArrayList<EventData>());
		return arrayList;
	}
	private boolean shouldSendEvent(final EventData e, final String userName) {
		return true;
		//( !e.confirmed && e.uid_owner.equals(userName) ) ||  ( !e.confirmed && e.tsOwner.equals(userName)) || ( !e.confirmed && e.tsOwner.equals(userName));

	}
	private void addToServer(final Sync sync) {
		if (sync.newEvents!=null) 
			for ( final EventData e : sync.newEvents) {
				ArrayList<EventData> arrayList = events.get(e.tsOwner);
				if (arrayList==null) arrayList = new ArrayList<EventData>();
				e.orderTS = serverTS++;
				arrayList.add(e);
				events.put(e.tsOwner,arrayList);
			}
	}

	private void showAllEvents() {
		final ArrayList<EventData> arrayList = new ArrayList<EventData>();
		for (final Entry<String, ArrayList<EventData>> e : events.entrySet()) {
			arrayList.addAll(e.getValue());
		}
		Collections.sort(arrayList);
		EventsListTableGUI.getInstance().setTable(new ListTable<EventData>(new ListTableModel<EventData>(arrayList)));
	}



}
